#region license
# This file is part of Grawlix.
# 
# Grawlix is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as
# published by the Free Software Foundation, either version 3 of
# the License, or (at your option) any later version.
# 
# Grawlix is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public
# License along with Grawlix.  If not, see
# <http://www.gnu.org/licenses/>.
#endregion

"""Figure out op_Inequality and Equals given op_Equality."""

namespace Grawlix.Meta
import System
import Boo.Lang.Compiler

transient class EqualityAttribute(ComparisonAttribute):
"""Generate equality statements based on one method."""
	
	override def Apply(targetNode as Ast.Node):
	"""
	Apply the AstAttribute to the specified node.
	Param targetNode: The node to apply to.
	"""
		if OverloadOperators is null:
			OverloadOperators = Ast.BoolLiteralExpression(false)
		super(targetNode)
	
	override def CreateBaseMethods([required] otherTag as TypeSystem.IType):
	"""
	Create base methods that always are needed.
	Param otherTag: The type to compare against.
	"""
		# Check for a previously defined equals method
		equals as Ast.Method = null
		paramIdentifier as Ast.ReferenceExpression
		for member as Ast.TypeMember in ParentDefinition.Members:
			# Equals method
			if member.Name == "Equals" and \
				member.NodeType == Ast.NodeType.Method:
				# One parameter
				method = member as Ast.Method
				if method.Parameters.Count == 1:
					# Parameter is of object type
					param = method.Parameters[0]
					paramType = param.Type
					if paramType is null:
						equals = method
						paramIdentifier = Ast.ReferenceExpression(param.Name)
						break
					NameResolutionService.ResolveTypeReference(paramType)
					paramTypeTag = TypeSystem.TypeSystemServices \
						.GetEntity(paramType) as TypeSystem.IType
					objectTag = TypeSystemServices.ObjectType
					if paramTypeTag == objectTag:
						equals = method
						paramIdentifier = Ast.ReferenceExpression(param.Name)
						break
		
		if equals is null:
			returnValue as Ast.Expression
			if ParentTag.IsValueType:
				returnValue = [| false |]
			else:
				returnValue = [| super($ObjIdentifier) |]
			equals = [|
				override def Equals($ObjIdentifier):
				"""
				Equality testing.
				Param $ObjIdentifier: The object to compare against.
				Returns: true if equal, else false.
				"""
					return $returnValue
			|]
			paramIdentifier = ObjIdentifier
			AddMember(equals)
		
		otherType = CodeBuilder.CreateTypeReference(otherTag)
		call = CreateMethodCall(
			Ast.SelfLiteralExpression(),
			[| cast($otherType, $ObjIdentifier) |])
		body = equals.Body
		equals.Body = [|
			if $paramIdentifier isa $otherType:
				return $call
			$body
		|]
	
	override def ImplementInterface([required] otherTag as TypeSystem.IType):
	"""
	Derive from interface and create the interface
	methods needed for comparison.
	Param otherTag: The type to compare against.
	"""
		otherType = CodeBuilder.CreateTypeReference(otherTag)
		baseEquatable = Ast.GenericTypeReference()
		baseEquatable.Name = "System.IEquatable"
		baseEquatable.GenericArguments.Add(otherType)
		AddBase(baseEquatable)
		
		call = CreateMethodCall(Ast.SelfLiteralExpression(), OtherIdentifier)
		generic = [|
			def Equals($OtherIdentifier as $otherType):
			"""
			Equality testing.
			Param $OtherReference: The object to compare against.
			Returns: true if equal, else false.
			"""
				return $call
		|]
		if not otherTag.IsValueType:
			PrependCheck(generic, OtherIdentifier, [| null |], [| false |])
		AddMember(generic)
	
	override def CreateOperators([required] leftTag as TypeSystem.IType,
	                             [required] rightTag as TypeSystem.IType):
	"""
	Create the operators for the type.
	Param leftTag: The left hand side of the operation.
	Param rightTag: The right hand side of the operation.
	"""
		leftType = CodeBuilder.CreateTypeReference(leftTag)
		rightType = CodeBuilder.CreateTypeReference(rightTag)
		call = CreateMethodCall(LhsIdentifier, RhsIdentifier)
		equality = [|
			static def op_Equality($LhsIdentifier as $leftType,
								   $RhsIdentifier as $rightType):
				return $call
		|]
		inequality = [|
			static def op_Inequality($LhsIdentifier as $leftType,
									 $RhsIdentifier as $rightType):
				return not op_Equality($LhsIdentifier, $RhsIdentifier)
		|]
		equality.Documentation = \
			"""
			Equality testing.
			Param ${LhsIdentifier.Name}: The left hand side of the comparison.
			Param ${RhsIdentifier.Name}: The right hand side of the comparison.
			Returns: true if equal, else false.
			"""
		inequality.Documentation = \
			"""
			Equality testing.
			Param ${LhsIdentifier.Name}: The left hand side of the comparison.
			Param ${RhsIdentifier.Name}: The right hand side of the comparison.
			Returns: true if equal, else false.
			"""
		
		# Reverse order
		if not rightTag.IsValueType:
			PrependCheck(equality, RhsIdentifier, [| null |], [| false |])
		if not leftTag.IsValueType:
			returnValue as Ast.Expression
			if rightTag.IsValueType:
				returnValue = [| false |]
			else:
				returnValue = [| $RhsIdentifier is null |]
			PrependCheck(equality, LhsIdentifier, [| null |], returnValue)
		AddMember(equality)
		AddMember(inequality)
		
	override def InvertResult([required] result as Ast.Expression):
	"""
	Invert the result of the method call.
	Param result: The expression to invert.
	Returns: The inverted expression.
	"""
		return result
	
	override def ShouldOverloadOperators():
	"""
	Decide whether operators should be produced.
	Returns: true if yes, false if no.
	"""
		return ParentTag.IsValueType or \
		       OverloadOperators.Value
	
	[property(OverloadOperators)]
	private _overloadOperators as Ast.BoolLiteralExpression
	"""
	Whether to overload reference type operators.
	Remarks: Normally for reference types, the equality operator should be,
		by convention, equivalent to reference equality, as defined
		by most .Net languages by default.
		
		For reference types this flag will force the generation of new
		equality operators which call the decorated method, instead of
		relying on the default reference equality operators.
		
		For value types or when inverse operators are requested,
		this flag is ignored, and operators will always be overloaded.
	"""
	
